// --------------------------------------------------------------------------------------------------------------------
// <copyright company="" file="ServerPool.cs">
//   
// </copyright>
// <summary>
//   The use socket.
// </summary>
// 
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Text;

namespace BeIT.MemCached
{
    /// <summary>
    /// The use socket.
    /// </summary>
    /// <param name="socket">
    /// The socket.
    /// </param>
    /// <typeparam name="T">
    /// </typeparam>
    internal delegate T UseSocket<T>(PooledSocket socket);

    /// <summary>
    /// The use socket.
    /// </summary>
    /// <param name="socket">
    /// The socket.
    /// </param>
    internal delegate void UseSocket(PooledSocket socket);

    /// <summary>
    /// The ServerPool encapsulates a collection of memcached servers and the associated SocketPool objects.
    /// This class contains the server-selection logic, and contains methods for executing a block of code on 
    /// a socket from the server corresponding to a given key.
    /// </summary>
    internal class ServerPool
    {
        /// <summary>
        /// The logger.
        /// </summary>
        private static LogAdapter logger = LogAdapter.GetLogger(typeof(ServerPool));

        // Expose the socket pools.
        /// <summary>
        /// The host list.
        /// </summary>
        private SocketPool[] hostList;

        /// <summary>
        /// Gets HostList.
        /// </summary>
        internal SocketPool[] HostList
        {
            get
            {
                return hostList;
            }
        }

        /// <summary>
        /// The host dictionary.
        /// </summary>
        private Dictionary<uint, SocketPool> hostDictionary;

        /// <summary>
        /// The host keys.
        /// </summary>
        private uint[] hostKeys;

        // Internal configuration properties
        /// <summary>
        /// The send receive timeout.
        /// </summary>
        private int sendReceiveTimeout = 2000;

        /// <summary>
        /// The connect timeout.
        /// </summary>
        private int connectTimeout = 2000;

        /// <summary>
        /// The max pool size.
        /// </summary>
        private uint maxPoolSize = 100;

        /// <summary>
        /// The min pool size.
        /// </summary>
        private uint minPoolSize = 5;

        /// <summary>
        /// The socket recycle age.
        /// </summary>
        private TimeSpan socketRecycleAge = TimeSpan.FromMinutes(30);

        /// <summary>
        /// Gets or sets SendReceiveTimeout.
        /// </summary>
        internal int SendReceiveTimeout
        {
            get
            {
                return sendReceiveTimeout;
            }

            set
            {
                sendReceiveTimeout = value;
            }
        }

        /// <summary>
        /// Gets or sets ConnectTimeout.
        /// </summary>
        internal int ConnectTimeout
        {
            get
            {
                return connectTimeout;
            }

            set
            {
                connectTimeout = value;
            }
        }

        /// <summary>
        /// Gets or sets MaxPoolSize.
        /// </summary>
        internal uint MaxPoolSize
        {
            get
            {
                return maxPoolSize;
            }

            set
            {
                maxPoolSize = value;
            }
        }

        /// <summary>
        /// Gets or sets MinPoolSize.
        /// </summary>
        internal uint MinPoolSize
        {
            get
            {
                return minPoolSize;
            }

            set
            {
                minPoolSize = value;
            }
        }

        /// <summary>
        /// Gets or sets SocketRecycleAge.
        /// </summary>
        internal TimeSpan SocketRecycleAge
        {
            get
            {
                return socketRecycleAge;
            }

            set
            {
                socketRecycleAge = value;
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ServerPool"/> class. 
        /// Internal constructor. This method takes the array of hosts and sets up an internal list of socketpools.
        /// </summary>
        /// <param name="hosts">
        /// The hosts.
        /// </param>
        internal ServerPool(string[] hosts)
        {
            hostDictionary = new Dictionary<uint, SocketPool>();
            List<SocketPool> pools = new List<SocketPool>();
            List<uint> keys = new List<uint>();
            foreach (string host in hosts)
            {
                // Create pool
                SocketPool pool = new SocketPool(this, host.Trim());

                // Create 250 keys for this pool, store each key in the hostDictionary, as well as in the list of keys.
                for (int i = 0; i < 250; i++)
                {
                    uint key =
                        BitConverter.ToUInt32(
                            new ModifiedFNV1_32().ComputeHash(Encoding.UTF8.GetBytes(host + "-" + i)), 0);
                    if (!hostDictionary.ContainsKey(key))
                    {
                        hostDictionary[key] = pool;
                        keys.Add(key);
                    }
                }

                pools.Add(pool);
            }

            // Hostlist should contain the list of all pools that has been created.
            hostList = pools.ToArray();

            // Hostkeys should contain the list of all key for all pools that have been created.
            // This array forms the server key continuum that we use to lookup which server a
            // given item key hash should be assigned to.
            keys.Sort();
            hostKeys = keys.ToArray();
        }

        /// <summary>
        /// Given an item key hash, this method returns the socketpool which is closest on the server key continuum.
        /// </summary>
        /// <param name="hash">
        /// The hash.
        /// </param>
        internal SocketPool GetSocketPool(uint hash)
        {
            // Quick return if we only have one host.
            if (hostList.Length == 1)
            {
                return hostList[0];
            }

            // New "ketama" host selection.
            int i = Array.BinarySearch(hostKeys, hash);

            // If not exact match...
            if (i < 0)
            {
                // Get the index of the first item bigger than the one searched for.
                i = ~i;

                // If i is bigger than the last index, it was bigger than the last item = use the first item.
                if (i >= hostKeys.Length)
                {
                    i = 0;
                }
            }

            return hostDictionary[hostKeys[i]];
        }

        /// <summary>
        /// The get socket pool.
        /// </summary>
        /// <param name="host">
        /// The host.
        /// </param>
        /// <returns>
        /// </returns>
        internal SocketPool GetSocketPool(string host)
        {
            return Array.Find(HostList, delegate(SocketPool socketPool) { return socketPool.Host == host; });
        }

        /// <summary>
        /// This method executes the given delegate on a socket from the server that corresponds to the given hash.
        /// If anything causes an error, the given defaultValue will be returned instead.
        /// This method takes care of disposing the socket properly once the delegate has executed.
        /// </summary>
        /// <typeparam name="T">
        /// </typeparam>
        /// <param name="hash">
        /// The hash.
        /// </param>
        /// <param name="defaultValue">
        /// The default Value.
        /// </param>
        /// <param name="use">
        /// The use.
        /// </param>
        internal T Execute<T>(uint hash, T defaultValue, UseSocket<T> use)
        {
            return Execute(GetSocketPool(hash), defaultValue, use);
        }

        /// <summary>
        /// The execute.
        /// </summary>
        /// <param name="pool">
        /// The pool.
        /// </param>
        /// <param name="defaultValue">
        /// The default value.
        /// </param>
        /// <param name="use">
        /// The use.
        /// </param>
        /// <typeparam name="T">
        /// </typeparam>
        /// <returns>
        /// </returns>
        internal T Execute<T>(SocketPool pool, T defaultValue, UseSocket<T> use)
        {
            PooledSocket sock = null;
            try
            {
                // Acquire a socket
                sock = pool.Acquire();

                // Use the socket as a parameter to the delegate and return its result.
                if (sock != null)
                {
                    return use(sock);
                }
            }
            catch (Exception e)
            {
                logger.Error("Error in Execute<T>: " + pool.Host, e);

                // Socket is probably broken
                if (sock != null)
                {
                    sock.Close();
                }
            }
            finally
            {
                if (sock != null)
                {
                    sock.Dispose();
                }
            }

            return defaultValue;
        }

        /// <summary>
        /// The execute.
        /// </summary>
        /// <param name="pool">
        /// The pool.
        /// </param>
        /// <param name="use">
        /// The use.
        /// </param>
        internal void Execute(SocketPool pool, UseSocket use)
        {
            PooledSocket sock = null;
            try
            {
                // Acquire a socket
                sock = pool.Acquire();

                // Use the socket as a parameter to the delegate and return its result.
                if (sock != null)
                {
                    use(sock);
                }
            }
            catch (Exception e)
            {
                logger.Error("Error in Execute: " + pool.Host, e);

                // Socket is probably broken
                if (sock != null)
                {
                    sock.Close();
                }
            }
            finally
            {
                if (sock != null)
                {
                    sock.Dispose();
                }
            }
        }

        /// <summary>
        /// This method executes the given delegate on all servers.
        /// </summary>
        /// <param name="use">
        /// The use.
        /// </param>
        internal void ExecuteAll(UseSocket use)
        {
            foreach (SocketPool socketPool in hostList)
            {
                Execute(socketPool, use);
            }
        }
    }
}